use crate::limits::LimitError;
use crate::scalar_fn::EvalScalar;

#[derive(Clone, Copy, Debug)]
pub enum IntegrationMethod {
    LeftRect,
    MidPoint,
    Trapezoid,
}

pub fn integrate<T>(
    a: f64,
    b: f64,
    func: &T,
    method: Option<IntegrationMethod>,
) -> Result<f64, LimitError>
where
    T: EvalScalar,
{
    let method = method.unwrap_or(IntegrationMethod::Trapezoid);
    let mut n = 32u64;
    let mut dx: f64 = (b - a) / (n as f64);
    let mut err = 1f64;
    let tol = 1e-8_f64;
    let mut old_sum = 0f64;
    let mut sum = 0f64;
    while err > tol {
        sum = (1..=n)
            .map(|i| {
                let xi = a + ((i as f64) - 1f64) * dx;
                match method {
                    IntegrationMethod::LeftRect => func.eval(xi) * dx,
                    IntegrationMethod::MidPoint => func.eval(xi + dx * 0.5) * dx,
                    IntegrationMethod::Trapezoid => 0.5 * dx * (func.eval(xi) + func.eval(xi + dx)),
                }
            })
            .sum();
        err = (sum - old_sum).abs();
        old_sum = sum;
        n <<= 1;
        dx = (b - a) / (n as f64);
    }
    println!(
        "Converged to {:?} with error of {:?} @ {:?}",
        sum,
        err,
        n >> 1
    );
    Ok(sum)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::math::{line, log};
    use crate::scalar_fn::ScalarFn;

    #[test]
    fn test_integrate_line() {
        let a = 0f64;
        let b = 1f64;
        let line = ScalarFn::new(line, None);
        match integrate(a, b, &line, Some(IntegrationMethod::MidPoint)) {
            Ok(x) => assert!((x - 0.5f64).abs() < 1e-6),
            Err(x) => {
                println!("Line integration failed with: {:?}", x);
                assert!(false)
            }
        }
    }

    #[test]
    fn test_integrate_log() {
        let a = 1f64;
        let b = 2f64;
        let log = ScalarFn::new(log, None);
        match integrate(a, b, &log, Some(IntegrationMethod::Trapezoid)) {
            Ok(_) => assert!(true),
            Err(x) => {
                println!("Line integration failed with: {:?}", x);
                assert!(false)
            }
        }
    }
}
